#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "pi_test.h"
#include "pcie_mem.h"

#define PI_CYCLE_REG_LSB         8          /* PI 周期寄存器最小计数单位，单位：ns */

enum pi_bar0_offset_t {
    PI_GET_CYCLE_BAR0_OFFSET = 0x98,        /* 读取 PI 周期寄存器偏移 */
    PI_GET_AC0_CYCLE_BAR0_OFFSET = 0x9C,    /* 读取 PI AC0 周期寄存器偏移 */
    PI_GET_AC1_CYCLE_BAR0_OFFSET = 0xA0,    /* 读取 PI AC1 周期寄存器偏移 */
    PI_SET_ID_BAR0_OFFSET = 0xA4,           /* 设置 PI ID 寄存器偏移 */
};

struct pi_info_t {
    uint8_t channel;                    /* 通道号 */
    uint8_t channel_min;                /* 最小通道号 */
    uint8_t channel_max;                /* 最大通道号 */
    double cycle;                       /* PI 周期值 */
};

static uint32_t get_single_pi(struct pcie_mem_ctx *ctx, uint8_t channel)
{
    volatile uint32_t *addr;
    uint32_t value;

    if (!ctx->map_base) {
        printf("错误: 内存未映射\n");
        return 0;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    *(volatile uint32_t *)((uintptr_t)addr + PI_SET_ID_BAR0_OFFSET) = channel;
    /* 注意：这里需要将字节偏移转换为 uint32_t 偏移 */
    value = *(addr + (PI_GET_CYCLE_BAR0_OFFSET / sizeof(uint32_t)));

    if (channel == 4) {
        value = *(addr + (PI_GET_AC0_CYCLE_BAR0_OFFSET / sizeof(uint32_t)));
        printf("读取: reg=0x%04X, val=0x%08X\n",
               PI_GET_AC0_CYCLE_BAR0_OFFSET, value);
    } else if (channel == 5) {
        value = *(addr + (PI_GET_AC1_CYCLE_BAR0_OFFSET / sizeof(uint32_t)));
        printf("读取: reg=0x%04X, val=0x%08X\n",
               PI_GET_AC1_CYCLE_BAR0_OFFSET, value);
    } else {
        printf("读取: reg=0x%04X, val=0x%08X\n",
               PI_GET_CYCLE_BAR0_OFFSET, value);
    }

    return value;
}

uint32_t get_single_pi_test(struct pcie_mem_ctx *ctx, struct pi_info_t pi_info)
{
    uint32_t reg_val = 0;

    if ((pi_info.channel >= pi_info.channel_min) && (pi_info.channel <= pi_info.channel_max)) {
        reg_val = get_single_pi(ctx, pi_info.channel);
        pi_info.cycle = 1000000000.0 / (reg_val * PI_CYCLE_REG_LSB);      /* ns 转换为频率，单位 Hz */
        printf("读取通道 %d 的 PI 输入值为 %dns\n", pi_info.channel, reg_val * PI_CYCLE_REG_LSB);
        printf("读取通道 %d 的 PI 输入值为 %.6lfHz\n", pi_info.channel, pi_info.cycle);
    } else {
        printf("参数错误: %d ≤ channel=%d ≤ %d\n",
               pi_info.channel_min, pi_info.channel, pi_info.channel_max);
        reg_val = 0;
    }

    return reg_val;
}

static int usage(int argc, char *argv[])
{
    if ((argc < 2) || (argc > 3)) {
        printf("用法: %s %s channel\n", argv[0], argv[1]);
        printf("示例: %s %s 0\n", argv[0], argv[1]);
        printf("用法: %s %s \n", argv[0], argv[1]);
        printf("示例: %s %s \n", argv[0], argv[1]);
        return EXIT_FAILURE;
    } else {
        return EXIT_SUCCESS;
    }
}

int pi_test(int argc, char *argv[])
{
    int ret = 0;
    const char *device_address = PCIE_PI_DEVICE_ADDRESS;
    uint32_t base_addr = PCIE_PI_BASE_ADDR_BAR0;
    size_t map_size = PCIE_PI_BAR0_MAP_SIZE;
    struct pcie_mem_ctx ctx = {
        .fd = -1,
        .map_base = NULL,
        .phys_base = 0,
        .map_size = 0
    };
    struct pi_info_t pi_info = {
        .channel = 0,
        .channel_min = 0,
        .channel_max = 3 + 2,
    };

    if (EXIT_SUCCESS != usage(argc, argv)) {
        return EXIT_FAILURE;
    }

    if (argc == 3) {
        pi_info.channel = atoi(argv[2]);
    } else if (argc == 2) {
        pi_info.channel = pi_info.channel_max + 1;
    }

    printf("正在获取通道 %d 的 PI...\n",
            pi_info.channel);

    if (true == is_pcie_device_enabled(device_address)) {
        printf("设备已启用\n");
    } else {
        printf("设备未启用\n");
        if (enable_pcie_device(device_address) == 0) {
            printf("设备启用成功\n");
        } else {
            printf("设备启用失败!\n");
            return EXIT_FAILURE;
        }
    }

    ret = init_pcie_mem(&ctx, base_addr, map_size);
    if (ret < 0) {
        printf("初始化内存映射失败!\n");
        return EXIT_FAILURE;
    }

    get_single_pi_test(&ctx, pi_info);

    cleanup_pcie_mem(&ctx);

    printf("通道 %d 的测试已完成\n", pi_info.channel);

    return EXIT_SUCCESS;
}
